package io.gitee.thant.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class ByteUtil {
	public static byte[] MD5(byte[] source) {
		byte[] secretBytes = null;
		try {
			secretBytes = MessageDigest.getInstance("md5").digest(source);
		} catch (Exception e) {
			return null;
		}
		return secretBytes;
	}

	public static String MD5String(byte[] source) {
		byte[] md5 = MD5(source);
		if (null == md5) return null;
		
		StringBuilder sb = new StringBuilder(32);
		String md5code = new BigInteger(1, md5).toString(16);
		for (int i=32-md5code.length(); i>0; --i) sb.append('0');
		return sb.append(md5code).toString();
	}

	public static byte[] base64Encode(byte[] source) {
		return Base64.getEncoder().encode(source);
	}

	public static byte[] base64Decode(byte[] source) {
		return Base64.getDecoder().decode(source);
	}

	public static byte[] AESEncode(byte[] source, byte[] key) {
		return cipher("AES/ECB/PKCS5Padding:AES:128", source, suitLength(key, 8, 'r', null), Cipher.ENCRYPT_MODE);
	}

	public static byte[] AESDecode(byte[] source, byte[] key) {
		return cipher("AES/ECB/PKCS5Padding:AES:128", source, suitLength(key, 8, 'r', null), Cipher.DECRYPT_MODE);
	}

	public static byte[] cipher(String def, byte[] source, byte[] encryptKey, int mode) {
		String[] defA = StringUtil.split(def, ":");
		String method = defA[0];
		String keytype = defA[1];
		int keysz = Integer.valueOf(defA[2]);
		try {
			//MD5散列，可以保证分布式环境下的一致性(碰撞几率很低)
			byte[] secretkey = suitLength(MD5(encryptKey), keysz/8, 'r', null);

			/*网上的方式，以encryptKey为源生成一个随机数，分布式环境(不同软硬件)结果可能不同，有风险
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			random.setSeed(encryptKey);
			KeyGenerator kgen = KeyGenerator.getInstance(keytype);
			kgen.init(keysz, random);
			SecretKey secretKey = kgen.generateKey();
			byte[] secretkey = secretKey.getEncoded();*/
			
			Cipher cipher = Cipher.getInstance(method);
			cipher.init(mode, new SecretKeySpec(secretkey, keytype));
			return cipher.doFinal(source);
		} catch (Exception e) {
			throw new RuntimeException(e.toString());
		}
	}
	
	public static byte[] suitLength(byte[] s, int len, char dir, byte[] padA) {
		if (len == s.length) return s;
		else {
			byte[] buf = new byte[len];
			if (s.length>len) {
				System.arraycopy(s, 0, buf, 0, len);
			} else {
				if (null == padA || 0 == padA.length) {
					padA = new byte[]{'!','@','#','$','%','^','&','*','x','w','f'};
				}
				int cpos, bpos, epos;
				if ('r' == dir || 'R' == dir) {
					//右补全
					cpos = 0;
					bpos = s.length;
					epos = len;
				} else {
					//左补全
					cpos = len - s.length;
					bpos = 0;
					epos = cpos;
				}
				System.arraycopy(s, 0, buf, cpos, s.length);
				for (int i=bpos, j=0; i<epos; ++i, ++j) {
					buf[i] = padA[j%padA.length];
				}
			}
			return buf;
		}
	}
	
	public static String toHexString(byte[] s, String join) {
		StringBuilder sb = new StringBuilder();
		if (s != null && s.length>0) {
			sb.append(Integer.toHexString(s[0] & 0xFF));
			if (StringUtil.isEmpty(join)) {
				for (int i=1; i<s.length; ++i) {
					sb.append(Integer.toHexString(s[i] & 0xFF));
				}
			} else {
				for (int i=1; i<s.length; ++i) {
					sb.append(join).append(Integer.toHexString(s[i] & 0xFF));
				}
			}
		}
		return sb.toString();
	}
	
	public static byte[] copy(byte[] s, int begin, int end) {
		if (begin<0) {
			begin = 0;
		} else if (begin>=s.length) {
			begin = s.length;
		}
		if (end<=0 || end>s.length) {
			end = s.length;
		}
		byte[] t;
		int len = end - begin;
		if (len>0) {
			t = new byte[len];
			System.arraycopy(s, begin, t, 0, len);
		} else {
			t = new byte[0];
		}
		return t;
	}

	public static byte[] copy(byte[] s, int begin) {
		return 	copy(s, begin, 0);
	}

	public static byte[] compress(byte[] sour) {
		if (null == sour || sour.length <= 0) {
			return null;
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try(GZIPOutputStream gzip = new GZIPOutputStream(out)) {
			gzip.write(sour);
			//gzip.finish(); //坑:zip流只有在close或finish之后才压缩完成
			//return out.toByteArray(); //所以要在try内返回的话，要手工finish
		} catch (Exception e) {
			LogHelper.error(e);
			return null;
		}
		return out.toByteArray(); //这里返回则不用finish，因为执行了close
	}

	public static byte[] decompress(byte[] sour) {
		if (sour == null || sour.length <= 0) {
			return null;
		}

		byte[] buf = new byte[1024];
		InputStream in = new ByteArrayInputStream(sour);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try(GZIPInputStream gzip = new GZIPInputStream(in, buf.length)) {
			int offset;
			while ((offset = gzip.read(buf)) != -1) {
				out.write(buf, 0, offset);
			}
			return out.toByteArray();
		} catch (Exception e) {
			LogHelper.error(e);
		}
		return null;
	}
}
